namespace UnitTests.Tests.ProjectSystem

open System
open System.IO
open System.Text.RegularExpressions
open NUnit.Framework
open UnitTests.TestLib.Utils.Asserts
open UnitTests.TestLib.Utils.FilesystemHelpers
open UnitTests.TestLib.ProjectSystem
open Microsoft.VisualStudio.FSharp.ProjectSystem


[<TestFixture>]
type RoundTrip() = 
    inherit TheTests()
    
    /////////////////////////////////
    // project helpers
    static let SaveProject(project : UnitTestingFSharpProjectNode) =
        project.Save(null, 1, 0u) |> ignore
    
    member this.``FsprojRoundtrip.PositiveTest``(origItems : MSBuildItems, expectedItems : MSBuildItems) =
        // test that opening with origItems and saving yields expectedItems
        this.MakeProjectAndDoWithProjectFile([], [], origItems.ToString(), (fun project fileName ->
            SaveProject(project)
            let fsprojFileText = File.ReadAllText(fileName)
            printfn "%s" fsprojFileText
            let regexStr = expectedItems.AsRegexString()
            TheTests.HelpfulAssertMatches '<' regexStr fsprojFileText
            if Regex.IsMatch(fsprojFileText, "<ItemGroup>\s*</ItemGroup>") then
                Assert.Fail("did not remove empty ItemGroups")
        ))
        // test idempotentcy (opening with previous-saved results causes no change)
        this.MakeProjectAndDoWithProjectFile([], [], expectedItems.ToString(), (fun project fileName ->
            SaveProject(project)
            let fsprojFileText = File.ReadAllText(fileName)
            printfn "%s" fsprojFileText
            let regexStr = expectedItems.AsRegexString()
            TheTests.HelpfulAssertMatches '<' regexStr fsprojFileText
            if Regex.IsMatch(fsprojFileText, "<ItemGroup>\s*</ItemGroup>") then
                Assert.Fail("did not remove empty ItemGroups")
        ))

    [<Test>]
    member public this.``FsprojRoundTrip.Basic.NonemptyFoldersRemoved.Case1``() =
        this.``FsprojRoundtrip.PositiveTest``(
            MSBuildItems [CompileItem @"bar.fs"
                          FolderItem @"Folder\"
                          CompileItem @"Folder\foo.fs"],
            MSBuildItems [CompileItem @"bar.fs"
                          CompileItem @"Folder\foo.fs"])

    [<Test>]
    member public this.``FsprojRoundTrip.Basic.NonemptyFoldersRemoved.Case2``() =
        this.``FsprojRoundtrip.PositiveTest``(
            MSBuildItems [CompileItem @"bar.fs"
                          FolderItem @"A\"
                          FolderItem @"A\B\"
                          FolderItem @"A\B\C\"
                          CompileItem @"A\B\C\foo.fs"
                          CompileItem @"A\qux.fs"],
            MSBuildItems [CompileItem @"bar.fs"
                          CompileItem @"A\B\C\foo.fs"
                          CompileItem @"A\qux.fs"])

    [<Test>]
    member public this.``FsprojRoundTrip.ComplexButLegalCase``() =
        let items = MSBuildItems [CompileItem @"A\B\foo.fs"
                                  CompileItem @"A\bar.fs"
                                  CompileItem @"A\C\D\qux.fs"
                                  CompileItem @"A\xyz.fs"
                                  CompileItem @"ddd.fs"
                                  CompileItem @"E\eee.fs"
                                  ]
        this.``FsprojRoundtrip.PositiveTest``(items, items)

    [<Test>]
    member public this.``FsprojRoundTrip.EmptyFoldersArePreservedWhenRestIsIdempotent``() =
        let items = MSBuildItems [CompileItem @"bar.fs"
                                  FolderItem @"A\Empty1\"
                                  CompileItem @"A\B\C\foo.fs"
                                  FolderItem @"A\B\Empty2\"
                                  CompileItem @"A\qux.fs"]
        this.``FsprojRoundtrip.PositiveTest``(items, items)

    [<Test>]
    member public this.``FsprojRoundTrip.EmptyFoldersArePreservedWhenRestIsLegalButNotIdempotent``() =
        let origItems = [CompileItem @"bar.fs"
                         FolderItem @"A\Empty1\"        
                         FolderItem @"A\B\"              // will get removed
                         CompileItem @"A\B\C\foo.fs"
                         FolderItem @"A\B\Empty2\"
                         CompileItem @"A\qux.fs"]
        let expectedItems = [CompileItem @"bar.fs"
                             FolderItem @"A\Empty1\"
                             CompileItem @"A\B\C\foo.fs"
                             FolderItem @"A\B\Empty2\"
                             CompileItem @"A\qux.fs"]
        this.``FsprojRoundtrip.PositiveTest``(MSBuildItems origItems, MSBuildItems expectedItems)

    member this.``Fsproj.NegativeTest``(items : MSBuildItems) =
        DoWithTempFile "Test.fsproj" (fun file ->
            File.AppendAllText(file, TheTests.SimpleFsprojText([], [], items.ToString()))
            let ex = new System.Exception()
            try
                use project = TheTests.CreateProject(file)
                let mutable node = project.FirstChild
                TheTests.PrintHierarchy(node)
                raise ex
            with
            | e when obj.ReferenceEquals(e,ex) -> Assert.Fail("did not expect to succeed creating project")
            | :? InvalidOperationException as e when e.ToString().Contains("rendered") ->
                printfn "As expected, failed to create project.  Reason: %s" (e.Message)
            | e -> Assert.Fail("failed to create project, but for wrong reason")
            ()
        )

    [<Test>]
    member public this.``FsprojRoundTrip.Basic.Invalid.Case1``() =
        let items = MSBuildItems [CompileItem @"A\B\C\foo.fs"
                                  CompileItem @"B\bar.fs"
                                  CompileItem @"A\B\D\qux.fs"]  // would cause A to be rendered twice
        this.``Fsproj.NegativeTest`` items

    [<Test>]
    member public this.``FsprojRoundTrip.Basic.Invalid.Case2``() =
        let items = MSBuildItems [CompileItem @"A\foo.fs"
                                  CompileItem @"bar.fs"
                                  CompileItem @"A\qux.fs"]  // would cause A to be rendered twice
        this.``Fsproj.NegativeTest`` items

    // REVIEW NYI: [<Test>]
    member public this.``FsprojRoundTrip.Basic.Invalid.Case3``() =
        let items = MSBuildItems [CompileItem @"A\foo.fs"
                                  FolderItem @"A\"           // <Folder> must be before anything below it
                                  CompileItem @"bar.fs"]
        this.``Fsproj.NegativeTest`` items
        
